package ace.module.oauth2.server.core.impl.converter.impl;

import ace.module.oauth2.server.core.impl.converter.Oauth2AuthorizationToOAuth2AuthorizationConverter;
import ace.module.oauth2.server.core.impl.converter.Oauth2ObjectJsonConverter;
import ace.module.oauth2.server.core.impl.dao.entity.Oauth2Authorization;
import java.time.Instant;
import java.util.*;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.stereotype.Component;

/**
 * @author caspar
 * @date 2024/2/21 16:18
 */
@AllArgsConstructor
@Component
public class Oauth2AuthorizationToOAuth2AuthorizationConverterImpl
    implements Oauth2AuthorizationToOAuth2AuthorizationConverter {
  private final Oauth2ObjectJsonConverter oauth2ObjectJsonConverter;

  @Override
  public OAuth2Authorization convert(
      Oauth2Authorization source, RegisteredClient registeredClient) {
    if (source == null) {
      return null;
    }

    OAuth2Authorization.Builder builder =
        OAuth2Authorization.withRegisteredClient(registeredClient);
    Map<String, Object> attributes = this.getAttributes(source.getAttributes());
    builder
        .id(source.getId())
        .authorizedScopes(this.getAuthorizedScopes(source.getAuthorizationScopes()))
        .authorizationGrantType(this.getAuthorizationGrantType(source.getAuthorizationGrantType()))
        .principalName(source.getPrincipalName())
        .attributes(attrs -> attrs.putAll(attributes));

    if (StringUtils.isNotBlank(source.getState())) {
      builder.attribute(OAuth2ParameterNames.STATE, source.getState());
    }

    Instant tokenIssuedAt;
    Instant tokenExpiresAt;
    if (StringUtils.isNotBlank(source.getAuthorizationCodeValue())) {
      tokenIssuedAt = Instant.ofEpochMilli(source.getAuthorizationCodeIssuedAt());
      tokenExpiresAt = Instant.ofEpochMilli(source.getAuthorizationCodeExpiresAt());
      Map<String, Object> authorizationCodeMetadata =
          this.toMap(source.getAuthorizationCodeMetadata());

      OAuth2AuthorizationCode authorizationCode =
          new OAuth2AuthorizationCode(
              source.getAuthorizationCodeValue(), tokenIssuedAt, tokenExpiresAt);
      builder.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata));
    }

    String accessTokenValue = source.getAccessTokenValue();
    if (org.springframework.util.StringUtils.hasText(accessTokenValue)) {
      tokenIssuedAt = Instant.ofEpochMilli(source.getAccessTokenIssuedAt());
      tokenExpiresAt = Instant.ofEpochMilli(source.getAccessTokenExpiresAt());
      Map<String, Object> accessTokenMetadata = this.toMap(source.getAccessTokenMetadata());
      OAuth2AccessToken.TokenType tokenType = null;
      if (OAuth2AccessToken.TokenType.BEARER
          .getValue()
          .equalsIgnoreCase(source.getAccessTokenType())) {
        tokenType = OAuth2AccessToken.TokenType.BEARER;
      }

      Set<String> scopes = Collections.emptySet();
      String accessTokenScopes = source.getAccessTokenScopes();
      if (accessTokenScopes != null) {
        scopes = org.springframework.util.StringUtils.commaDelimitedListToSet(accessTokenScopes);
      }
      OAuth2AccessToken accessToken =
          new OAuth2AccessToken(tokenType, accessTokenValue, tokenIssuedAt, tokenExpiresAt, scopes);
      builder.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata));
    }

    String oidcIdTokenValue = source.getOidcIdTokenValue();
    if (org.springframework.util.StringUtils.hasText(oidcIdTokenValue)) {
      tokenIssuedAt = Instant.ofEpochMilli(source.getOidcIdTokenIssuedAt());
      tokenExpiresAt = Instant.ofEpochMilli(source.getOidcIdTokenExpiresAt());
      Map<String, Object> oidcTokenMetadata = this.toMap(source.getOidcIdTokenMetadata());

      OidcIdToken oidcToken =
          new OidcIdToken(
              oidcIdTokenValue,
              tokenIssuedAt,
              tokenExpiresAt,
              (Map<String, Object>)
                  oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME));
      builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata));
    }

    String refreshTokenValue = source.getRefreshTokenValue();
    if (org.springframework.util.StringUtils.hasText(refreshTokenValue)) {
      tokenIssuedAt = Instant.ofEpochMilli(source.getRefreshTokenIssuedAt());
      tokenExpiresAt = null;
      Instant refreshTokenExpiresAt =
          Optional.ofNullable(source.getRefreshTokenExpiresAt())
              .map(Instant::ofEpochMilli)
              .orElse(null);
      if (refreshTokenExpiresAt != null) {
        tokenExpiresAt = refreshTokenExpiresAt;
      }
      Map<String, Object> refreshTokenMetadata = this.toMap(source.getRefreshTokenMetadata());

      OAuth2RefreshToken refreshToken =
          new OAuth2RefreshToken(refreshTokenValue, tokenIssuedAt, tokenExpiresAt);
      builder.token(refreshToken, (metadata) -> metadata.putAll(refreshTokenMetadata));
    }
    return builder.build();
  }

  @SneakyThrows
  private Map<String, Object> getAttributes(String attributes) {
    return this.toMap(attributes);
  }

  @SneakyThrows
  private Map<String, Object> toMap(String attributes) {
    return this.oauth2ObjectJsonConverter.toMap(attributes);
  }

  private AuthorizationGrantType getAuthorizationGrantType(String authorizationGrantType) {
    return new AuthorizationGrantType(authorizationGrantType);
  }

  private Set<String> getAuthorizedScopes(String authorizedScopesString) {
    if (authorizedScopesString != null) {
      return org.springframework.util.StringUtils.commaDelimitedListToSet(authorizedScopesString);
    }
    return Collections.emptySet();
  }
}
